项目配置
1 依赖注入
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.zsecode</groupId>
<artifactId>yeb</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>yebserver</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1.tmp</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.9.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>com.github.axet</groupId>
<artifactId>kaptcha</artifactId>
<version>0.0.9</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.2</version>
</plugin>
</plugins>
</build>
</project>
SecurityConfig
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private IAdminService adminService;
@Autowired
private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
@Autowired
private RestAuthorizationEntryPoint restAuthorizationEntryPoint;
@Autowired
private CustomFilter customFilter;
@Autowired
private CustomUrlDecisionManager customUrlDecisionManager;
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(
"/login","/logout","/css/**","/js/**","/index.html","/favicon.ioc","/doc.html",
"/webjars/**","/swagger-resources/**","/v2/api-docs/**","/captcha"
);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.anyRequest().authenticated()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
o.setAccessDecisionManager(customUrlDecisionManager);
o.setSecurityMetadataSource(customFilter);
return o;
}
})
.and().headers().cacheControl();
http.addFilterBefore(jwtAuthorizationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
http.exceptionHandling().accessDeniedHandler(restfulAccessDeniedHandler)
.authenticationEntryPoint(restAuthorizationEntryPoint);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
}
@Override
@Bean
public UserDetailsService userDetailsService(){
return username->{
Admin admin = adminService.getAdminByUserName(username);
if (null != admin){
admin.setRoles(adminService.getRoles(admin.getId()));
return admin;
}
throw new UsernameNotFoundException("用户名或者密码错误");
};
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public JwtAuthorizationTokenFilter jwtAuthorizationTokenFilter(){
return new JwtAuthorizationTokenFilter();
}
}
JwtAuthorizationTokenFilter
public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {
@Value("${jwt.tokenHeader}")
private String tokenHeader;
@Value("${jwt.tokenHead}")
private String tokenHead;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
System.out.println("JwtAuthorizationTokenFilter");
String header = httpServletRequest.getHeader(tokenHeader);
if (null != header && header.startsWith(tokenHead)){
String autoToken = header.substring(tokenHead.length());
String username = jwtTokenUtil.getUserNameFromToken(autoToken);
if (null != username && null == SecurityContextHolder.getContext().getAuthentication()){
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(autoToken,userDetails)){
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
}
filterChain.doFilter(httpServletRequest,httpServletResponse);
}
}
CustomFilter
@Component
public class CustomFilter implements FilterInvocationSecurityMetadataSource {
@Autowired
private IMenuService menuService;
AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
System.out.println("CustomFilter");
String requestUrl = ((FilterInvocation) o).getRequestUrl();
List<Menu> menus = menuService.getMenusWithRole();
for (Menu menu: menus){
if (antPathMatcher.match(menu.getUrl(),requestUrl)){
String[] strings = menu.getRoles().stream().map(Role::getName).toArray(String[]::new);
return SecurityConfig.createList(strings);
}
}
return SecurityConfig.createList("ROLE_LOGIN");
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> aClass) {
return false;
}
}
CustomUrlDecisionManager
@Component
public class CustomUrlDecisionManager implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
System.out.println("CustomUrlDecisionManager");
for (ConfigAttribute configAttribute : collection){
String needRole = configAttribute.getAttribute();
System.out.println(needRole);
if ("ROLE_LOGIN".equals(needRole)){
if (authentication instanceof AnonymousAuthenticationToken){
throw new AccessDeniedException("尚未登录");
}else{
return;
}
}
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority authority: authorities){
if (authority.getAuthority().equals(needRole)){
return;
}
}
}
throw new AccessDeniedException("权限不足");
}
@Override
public boolean supports(ConfigAttribute configAttribute) {
return false;
}
@Override
public boolean supports(Class<?> aClass) {
return false;
}
}
RestAuthorizationEntryPoint
@Component
public class RestAuthorizationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setCharacterEncoding("utf-8");
httpServletResponse.setContentType("application/json");
PrintWriter out = httpServletResponse.getWriter();
RespBean respBean = RespBean.error("尚未登陆,请登录");
respBean.setCode(401);
out.write(new ObjectMapper().writeValueAsString(respBean));
out.flush();
out.close();
}
}
RestfulAccessDeniedHandler
@Component
public class RestfulAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json");
PrintWriter out = httpServletResponse.getWriter();
RespBean respBean = RespBean.error("没有权限");
respBean.setCode(403);
out.write(new ObjectMapper().writeValueAsString(respBean));
out.flush();
out.close();
}
}
JwtTokenUtil
@Component
public class JwtTokenUtil {
private static final String ClAIM_KEY_USERNAME="sub";
private static final String CLAIM_KEY_CREATE="created";
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
public String generateToken(UserDetails userDetails){
Map<String,Object> claims = new HashMap<>();
claims.put(ClAIM_KEY_USERNAME,userDetails.getUsername());
claims.put(CLAIM_KEY_CREATE,new Date());
return generateToken(claims);
}
private String generateToken(Map<String,Object> claims){
return Jwts.builder()
.setClaims(claims)
.setExpiration(generateExpirationDate())
.signWith(SignatureAlgorithm.HS512,secret)
.compact();
}
public String getUserNameFromToken(String token){
String username;
try {
Claims claims = getClaimsFormToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
e.printStackTrace();
}
return username;
}
public boolean validateToken(String token,UserDetails userDetails){
String username = getUserNameFromToken(token);
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
}
public boolean canRefresh(String token){
return !isTokenExpired(token);
}
public String refreshToken(String token){
Claims claims = getClaimsFormToken(token);
claims.put(CLAIM_KEY_CREATE,new Date());
return generateToken(claims);
}
private boolean isTokenExpired(String token){
Date expireDate = getExpirationDateFromToken(token);
return expireDate.before(new Date());
}
private Date getExpirationDateFromToken(String token) {
Claims claims = getClaimsFormToken(token);
return claims.getExpiration();
}
private Claims getClaimsFormToken(String token) {
Claims claims = null;
try {
claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
} catch (Exception e) {
e.printStackTrace();
}
return claims;
}
private Date generateExpirationDate() {
return new Date(System.currentTimeMillis()+expiration*1000);
}
}