Browse Source

import spring securiety

main
maochaoying 2 years ago
parent
commit
ba0e93892a
  1. 5
      pom.xml
  2. 11
      src/main/java/com/iflytop/nuclear/NuclearApplication.java
  3. 68
      src/main/java/com/iflytop/nuclear/config/SecurityConfig.java
  4. 56
      src/main/java/com/iflytop/nuclear/controller/AccountController.java
  5. 80
      src/main/java/com/iflytop/nuclear/entity/JwtUser.java
  6. 25
      src/main/java/com/iflytop/nuclear/exception/JWTAccessDeniedHandler.java
  7. 28
      src/main/java/com/iflytop/nuclear/exception/JWTAuthenticationEntryPoint.java
  8. 27
      src/main/java/com/iflytop/nuclear/exception/TokenIsExpiredException.java
  9. 77
      src/main/java/com/iflytop/nuclear/filter/JWTAuthenticationFilter.java
  10. 73
      src/main/java/com/iflytop/nuclear/filter/JWTAuthorizationFilter.java
  11. 13
      src/main/java/com/iflytop/nuclear/mapper/AccountMapper.java
  12. 26
      src/main/java/com/iflytop/nuclear/model/Account.java
  13. 16
      src/main/java/com/iflytop/nuclear/service/AccountService.java
  14. 28
      src/main/java/com/iflytop/nuclear/service/UserDetailsServiceImpl.java
  15. 51
      src/main/java/com/iflytop/nuclear/service/impl/AccountServiceImpl.java
  16. 68
      src/main/java/com/iflytop/nuclear/utils/JwtTokenUtils.java
  17. 2
      src/main/resources/application-dev.yml

5
pom.xml

@ -50,6 +50,11 @@
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3</version>
</dependency>
<!--spring security权限依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>

11
src/main/java/com/iflytop/nuclear/NuclearApplication.java

@ -1,15 +1,22 @@
package com.iflytop.nuclear;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.Banner;
import org.springframework.boot.ResourceBanner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.core.io.ClassPathResource;
@SpringBootApplication
@MapperScan("com.iflytop.boditech.mapper")
@MapperScan("com.iflytop.nuclear.mapper")
public class NuclearApplication {
public static void main(String[] args) {
SpringApplication.run(NuclearApplication.class, args);
SpringApplicationBuilder builder = new SpringApplicationBuilder(NuclearApplication.class);
builder.bannerMode(Banner.Mode.CONSOLE);
builder.banner(new ResourceBanner(new ClassPathResource("banner.txt")));
builder.run(args);
}
}

68
src/main/java/com/iflytop/nuclear/config/SecurityConfig.java

@ -0,0 +1,68 @@
package com.iflytop.nuclear.config;
import com.iflytop.nuclear.exception.JWTAccessDeniedHandler;
import com.iflytop.nuclear.exception.JWTAuthenticationEntryPoint;
import com.iflytop.nuclear.filter.JWTAuthenticationFilter;
import com.iflytop.nuclear.filter.JWTAuthorizationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
/**
* @author cool
* @desc spring-security 配置类
*/
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("userDetailsServiceImpl")
private UserDetailsService userDetailsService;
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.authorizeRequests()
// 注册接口需要ADMIN用户才能访问
.antMatchers("/account/register").hasRole("ADMIN")
// 其他都放行了
.anyRequest().permitAll()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
// 不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling().authenticationEntryPoint(new JWTAuthenticationEntryPoint())
.accessDeniedHandler(new JWTAccessDeniedHandler()); //添加无权限时的处理
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
}

56
src/main/java/com/iflytop/nuclear/controller/AccountController.java

@ -0,0 +1,56 @@
package com.iflytop.nuclear.controller;
import com.iflytop.nuclear.model.Account;
import com.iflytop.nuclear.service.AccountService;
import com.iflytop.nuclear.utils.ResponseData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* @author cool
* @desc 用户接口
*/
@Slf4j
@RestController
@RequestMapping("/account")
public class AccountController {
@Autowired
AccountService accountService;
/**
* 查询用户列表
* @return
*/
@GetMapping("/list")
public ResponseData getAccountList() {
log.info("-----------------查询账户列表开始-----------------");
// 筛除password
// TODO
List<Account> accounts = accountService.list();
log.info("-----------------查询账户列表结束-----------------");
return ResponseData.success(accounts);
}
/**
* 注册接口需要有ADMIN权限
* @param registerUser
* @return
*/
@PostMapping("/register")
@PreAuthorize("hasRole('ADMIN')")
public ResponseData registerAccount(@RequestBody Map<String,String> registerUser) {
log.info("-----------------注册账户开始-----------------");
boolean register = accountService.register(registerUser.get("username"), registerUser.get("password"));
if (register) {
log.info("-----------------注册账户成功-----------------");
return ResponseData.success();
}
return ResponseData.fail("注册失败");
}
}

80
src/main/java/com/iflytop/nuclear/entity/JwtUser.java

@ -0,0 +1,80 @@
package com.iflytop.nuclear.entity;
import com.iflytop.nuclear.model.Account;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.Collections;
/**
* @author cool
* @desc 自定义实现UserDetails
*/
public class JwtUser implements UserDetails {
private Long id;
private String username;
private String password;
private Collection<? extends GrantedAuthority> authorities;
public JwtUser() {
}
// 写一个能直接使用user创建jwtUser的构造器
public JwtUser(Account user) {
id = user.getId();
username = user.getUsername();
password = user.getPassword();
authorities = Collections.singleton(new SimpleGrantedAuthority(user.getRole()));
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
// 账号是否未过期
@Override
public boolean isAccountNonExpired() {
return true;
}
// 账号是否未锁定
@Override
public boolean isAccountNonLocked() {
return true;
}
// 账号凭证是否未过期
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public String toString() {
return "JwtUser{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", authorities=" + authorities +
'}';
}
}

25
src/main/java/com/iflytop/nuclear/exception/JWTAccessDeniedHandler.java

@ -0,0 +1,25 @@
package com.iflytop.nuclear.exception;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author cool
* @description: 没有访问权限
*/
public class JWTAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json; charset=utf-8");
httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
String reason = "统一处理,原因:" + e.getMessage();
httpServletResponse.getWriter().write(new ObjectMapper().writeValueAsString(reason));
}
}

28
src/main/java/com/iflytop/nuclear/exception/JWTAuthenticationEntryPoint.java

@ -0,0 +1,28 @@
package com.iflytop.nuclear.exception;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author cool
* @description: 没有携带token或者token无效
*/
public class JWTAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
String reason = "统一处理,原因:" + authException.getMessage();
response.getWriter().write(new ObjectMapper().writeValueAsString(reason));
}
}

27
src/main/java/com/iflytop/nuclear/exception/TokenIsExpiredException.java

@ -0,0 +1,27 @@
package com.iflytop.nuclear.exception;
/**
* @author cool
* @description: 自定义异常
*/
public class TokenIsExpiredException extends Exception {
public TokenIsExpiredException() {
}
public TokenIsExpiredException(String message) {
super(message);
}
public TokenIsExpiredException(String message, Throwable cause) {
super(message, cause);
}
public TokenIsExpiredException(Throwable cause) {
super(cause);
}
public TokenIsExpiredException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

77
src/main/java/com/iflytop/nuclear/filter/JWTAuthenticationFilter.java

@ -0,0 +1,77 @@
package com.iflytop.nuclear.filter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.iflytop.nuclear.entity.JwtUser;
import com.iflytop.nuclear.model.Account;
import com.iflytop.nuclear.utils.JwtTokenUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
/**
* @author cool
* @desc 该过滤器用于获取用户登录的信息
*/
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
super.setFilterProcessesUrl("/account/login");
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
// 从输入流中获取到登录的信息
try {
Account loginUser = new ObjectMapper().readValue(request.getInputStream(), Account.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginUser.getUsername(), loginUser.getPassword(), new ArrayList<>())
);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
// 成功验证后调用的方法
// 如果验证成功就生成token并返回
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws IOException, ServletException {
JwtUser jwtUser = (JwtUser) authResult.getPrincipal();
System.out.println("jwtUser:" + jwtUser.toString());
String role = "";
Collection<? extends GrantedAuthority> authorities = jwtUser.getAuthorities();
for (GrantedAuthority authority : authorities){
role = authority.getAuthority();
}
String token = JwtTokenUtils.createToken(jwtUser.getUsername(), role);
// 返回创建成功的token
// 但是这里创建的token只是单纯的token
// 按照jwt的规定最后请求的时候应该是 `Bearer token`
response.setHeader("token", JwtTokenUtils.TOKEN_PREFIX + token);
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
response.getWriter().write("authentication failed, reason: " + failed.getMessage());
}
}

73
src/main/java/com/iflytop/nuclear/filter/JWTAuthorizationFilter.java

@ -0,0 +1,73 @@
package com.iflytop.nuclear.filter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.iflytop.nuclear.exception.TokenIsExpiredException;
import com.iflytop.nuclear.utils.JwtTokenUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;
/**
* @author cool
* @desc 鉴权
*/
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
String tokenHeader = request.getHeader(JwtTokenUtils.TOKEN_HEADER);
// 如果请求头中没有Authorization信息则直接放行了
if (tokenHeader == null || !tokenHeader.startsWith(JwtTokenUtils.TOKEN_PREFIX)) {
chain.doFilter(request, response);
return;
}
// 如果请求头中有token则进行解析并且设置认证信息
try {
SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader));
} catch (TokenIsExpiredException e) {
//返回json形式的错误信息
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
String reason = "统一处理,原因:" + e.getMessage();
response.getWriter().write(new ObjectMapper().writeValueAsString(reason));
response.getWriter().flush();
return;
}
super.doFilterInternal(request, response, chain);
}
// 这里从token中获取用户信息并新建一个token
private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) throws TokenIsExpiredException {
String token = tokenHeader.replace(JwtTokenUtils.TOKEN_PREFIX, "");
boolean expiration = JwtTokenUtils.isExpiration(token);
if (expiration) {
throw new TokenIsExpiredException("token超时了");
} else {
String username = JwtTokenUtils.getUsername(token);
String role = JwtTokenUtils.getUserRole(token);
if (username != null) {
return new UsernamePasswordAuthenticationToken(username, null,
Collections.singleton(new SimpleGrantedAuthority(role))
);
}
}
return null;
}
}

13
src/main/java/com/iflytop/nuclear/mapper/AccountMapper.java

@ -0,0 +1,13 @@
package com.iflytop.nuclear.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.iflytop.nuclear.model.Account;
import org.apache.ibatis.annotations.Mapper;
/**
* @author cool
*/
@Mapper
public interface AccountMapper extends BaseMapper<Account> {
}

26
src/main/java/com/iflytop/nuclear/model/Account.java

@ -0,0 +1,26 @@
package com.iflytop.nuclear.model;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author cool
* @desc 账户model
*/
@Data
@TableName("account")
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Account {
@TableId
private Long id;
private String username;
private String password;
private String role;
private String nickname;
}

16
src/main/java/com/iflytop/nuclear/service/AccountService.java

@ -0,0 +1,16 @@
package com.iflytop.nuclear.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.iflytop.nuclear.model.Account;
import org.springframework.transaction.annotation.Transactional;
/**
* @author cool
*/
@Transactional
public interface AccountService extends IService<Account> {
boolean register(String username, String password);
Account findByUsername(String username);
}

28
src/main/java/com/iflytop/nuclear/service/UserDetailsServiceImpl.java

@ -0,0 +1,28 @@
package com.iflytop.nuclear.service;
import com.iflytop.nuclear.entity.JwtUser;
import com.iflytop.nuclear.model.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
/**
* @author cool
* @desc 使用springSecurity需要实现UserDetailsService接口供权限框架调用
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
@Lazy
AccountService accountService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Account user = accountService.findByUsername(username);
return new JwtUser(user);
}
}

51
src/main/java/com/iflytop/nuclear/service/impl/AccountServiceImpl.java

@ -0,0 +1,51 @@
package com.iflytop.nuclear.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.iflytop.nuclear.mapper.AccountMapper;
import com.iflytop.nuclear.model.Account;
import com.iflytop.nuclear.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
/**
* @author cool
*/
@Service
public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> implements AccountService {
@Autowired
@Lazy
private BCryptPasswordEncoder bCryptPasswordEncoder;
/**
* 注册用户
* @param username
* @param password
* @return
*/
@Override
public boolean register(String username, String password) {
Account account = Account.builder()
.username(username)
.password(bCryptPasswordEncoder.encode(password))
.role("ROLE_USER")
.build();
return this.save(account);
}
/**
* 根据用户名查找用户
* @param username
* @return
*/
@Override
public Account findByUsername(String username) {
QueryWrapper<Account> accountQueryWrapper = new QueryWrapper<>();
accountQueryWrapper.eq("username", username);
Account account = this.getOne(accountQueryWrapper);
return account;
}
}

68
src/main/java/com/iflytop/nuclear/utils/JwtTokenUtils.java

@ -0,0 +1,68 @@
package com.iflytop.nuclear.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
/**
* @author cool
* @desc 操作jwt token 的工具类
*/
public class JwtTokenUtils {
public static final String TOKEN_HEADER = "Authorization";
public static final String TOKEN_PREFIX = "Bearer ";
private static final String SECRET = "eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE2NDg5ODg1MjAsInN1YiI6ImFkbWluIiwiY3JlYXR";
private static final String ISS = "iflytop";
// 角色的key
private static final String ROLE_CLAIMS = "rol";
// 过期时间是3600秒既是1个小时
private static final long EXPIRATION = 3600L;
// 创建token
public static String createToken(String username, String role) {
HashMap<String, Object> map = new HashMap<>();
map.put(ROLE_CLAIMS, role);
return Jwts.builder()
.signWith(SignatureAlgorithm.HS512, SECRET)
.setClaims(map)
.setIssuer(ISS)
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION * 1000))
.compact();
}
// 从token中获取用户名
public static String getUsername(String token){
return getTokenBody(token).getSubject();
}
// 获取用户角色
public static String getUserRole(String token){
return (String) getTokenBody(token).get(ROLE_CLAIMS);
}
// 是否已过期
public static boolean isExpiration(String token) {
try {
return getTokenBody(token).getExpiration().before(new Date());
} catch (ExpiredJwtException e) {
return true;
}
}
private static Claims getTokenBody(String token){
return Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody();
}
}

2
src/main/resources/application-dev.yml

@ -1,7 +1,7 @@
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/boditech?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
url: jdbc:mysql://127.0.0.1:3306/nuclear?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: root
driverClassName: com.mysql.cj.jdbc.Driver
Loading…
Cancel
Save