1. 项目目录
2. pom.xml
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter-web</ artifactId>
< version> 2.6.3</ version>
</ dependency>
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter-security</ artifactId>
< version> 2.6.3</ version>
</ dependency>
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter-data-redis</ artifactId>
< version> 2.6.3</ version>
</ dependency>
< dependency>
< groupId> com.baomidou</ groupId>
< artifactId> mybatis-plus-boot-starter</ artifactId>
< version> 3.5.1</ version>
</ dependency>
< dependency>
< groupId> mysql</ groupId>
< artifactId> mysql-connector-java</ artifactId>
< version> 8.0.26</ version>
</ dependency>
< dependency>
< groupId> com.alibaba</ groupId>
< artifactId> fastjson</ artifactId>
< version> 2.0.46</ version>
</ dependency>
< dependency>
< groupId> org.projectlombok</ groupId>
< artifactId> lombok</ artifactId>
< version> 1.18.30</ version>
</ dependency>
< dependency>
< groupId> io.jsonwebtoken</ groupId>
< artifactId> jjwt</ artifactId>
< version> 0.9.1</ version>
< exclusions>
< exclusion>
< groupId> com.fasterxml.jackson.core</ groupId>
< artifactId> jackson-databind</ artifactId>
</ exclusion>
</ exclusions>
</ dependency>
3. application.yml
spring :
datasource :
driver-class-name : com.mysql.cj.jdbc.Driver
url : jdbc: mysql: //127.0.0.1: 3306/demo? useUnicode=true&characterEncoding=UTF- 8
username : root
password : root
mybatis-plus :
mapper-locations : classpath: /mapper/*.xml
type-aliases-package : com.cnbai.entity
global-config :
db-config :
id-type : input
db-column-underline : true
refresh-mapper : true
configuration :
map-underscore-to-camel-case : true
call-setters-on-nulls : true
log-impl : org.apache.ibatis.logging.stdout.StdOutImpl
4. 创建配置类
CorsConfig
package com. cnbai. config ;
import org. springframework. context. annotation. Configuration ;
import org. springframework. web. servlet. config. annotation. CorsRegistry ;
import org. springframework. web. servlet. config. annotation. WebMvcConfigurer ;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings ( CorsRegistry registry) {
registry. addMapping ( "/**" )
. allowedOriginPatterns ( "*" )
. allowCredentials ( true )
. allowedMethods ( "GET" , "POST" , "DELETE" , "PUT" )
. allowedHeaders ( "*" )
. maxAge ( 3600 ) ;
}
}
DiscoverSecurityConfig
package com. cnbai. config ;
import com. cnbai. handler. JwtAuthenticationTokenFilter ;
import org. springframework. context. annotation. Bean ;
import org. springframework. context. annotation. Configuration ;
import org. springframework. security. authentication. AuthenticationManager ;
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. builders. WebSecurity ;
import org. springframework. security. config. annotation. web. configuration. WebSecurityConfigurerAdapter ;
import org. springframework. security. config. http. SessionCreationPolicy ;
import org. springframework. security. crypto. bcrypt. BCryptPasswordEncoder ;
import org. springframework. security. crypto. password. PasswordEncoder ;
import org. springframework. security. web. AuthenticationEntryPoint ;
import org. springframework. security. web. access. AccessDeniedHandler ;
import org. springframework. security. web. authentication. UsernamePasswordAuthenticationFilter ;
import javax. annotation. Resource ;
@Configuration
@EnableGlobalMethodSecurity ( prePostEnabled = true )
public class DiscoverSecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Resource
private AuthenticationEntryPoint authenticationEntryPoint;
@Resource
private AccessDeniedHandler accessDeniedHandler;
@Bean
public PasswordEncoder passwordEncoder ( ) {
return new BCryptPasswordEncoder ( ) ;
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean ( ) throws Exception {
return super . authenticationManagerBean ( ) ;
}
@Override
public void configure ( WebSecurity web) throws Exception {
web. ignoring ( )
. antMatchers ( "/resources/**" )
. antMatchers ( "/static/**" ) ;
}
@Override
protected void configure ( HttpSecurity http) throws Exception {
http. csrf ( ) . disable ( )
. sessionManagement ( ) . sessionCreationPolicy ( SessionCreationPolicy . STATELESS )
. and ( )
. authorizeRequests ( )
. antMatchers ( "/user/login" ) . anonymous ( )
. anyRequest ( ) . authenticated ( ) ;
http. addFilterBefore ( jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter . class ) ;
http. exceptionHandling ( )
. authenticationEntryPoint ( authenticationEntryPoint)
. accessDeniedHandler ( accessDeniedHandler) ;
http. cors ( ) ;
}
}
MybatisPlusConfig
package com. cnbai. config ;
import com. baomidou. mybatisplus. annotation. DbType ;
import com. baomidou. mybatisplus. autoconfigure. MybatisPlusProperties ;
import com. baomidou. mybatisplus. extension. plugins. MybatisPlusInterceptor ;
import com. baomidou. mybatisplus. extension. plugins. inner. PaginationInnerInterceptor ;
import org. springframework. boot. context. properties. ConfigurationProperties ;
import org. springframework. context. annotation. Bean ;
import org. springframework. context. annotation. Configuration ;
import org. springframework. context. annotation. Primary ;
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor ( ) {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor ( ) ;
interceptor. addInnerInterceptor ( new PaginationInnerInterceptor ( DbType . MYSQL ) ) ;
return interceptor;
}
@Primary
@Bean
@ConfigurationProperties ( "mybatis-plus" )
public MybatisPlusProperties mybatisPlusProperties ( ) {
return new MybatisPlusProperties ( ) ;
}
}
5. 创建实体类
LoginUser
package com. cnbai. entity ;
import com. alibaba. fastjson. annotation. JSONField ;
import lombok. Data ;
import lombok. NoArgsConstructor ;
import org. springframework. security. core. GrantedAuthority ;
import org. springframework. security. core. authority. SimpleGrantedAuthority ;
import org. springframework. security. core. userdetails. UserDetails ;
import java. util. ArrayList ;
import java. util. Collection ;
import java. util. List ;
@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {
private User user;
private List < String > permissions;
public LoginUser ( User user, List < String > permissions) {
this . user = user;
this . permissions = permissions;
}
@JSONField ( serialize = false )
private List < SimpleGrantedAuthority > authorities;
@Override
public Collection < ? extends GrantedAuthority > getAuthorities ( ) {
if ( authorities != null ) {
return authorities;
}
authorities = new ArrayList < > ( ) ;
for ( String permission : permissions) {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority ( permission) ;
authorities. add ( authority) ;
}
return authorities;
}
@Override
public String getPassword ( ) {
return user. getPassword ( ) ;
}
@Override
public String getUsername ( ) {
return user. getUsername ( ) ;
}
@Override
public boolean isAccountNonExpired ( ) {
return true ;
}
@Override
public boolean isAccountNonLocked ( ) {
return true ;
}
@Override
public boolean isCredentialsNonExpired ( ) {
return true ;
}
@Override
public boolean isEnabled ( ) {
return true ;
}
}
Menu
package com. cnbai. entity ;
import com. baomidou. mybatisplus. annotation. TableId ;
import com. baomidou. mybatisplus. annotation. TableName ;
import com. fasterxml. jackson. annotation. JsonInclude ;
import lombok. AllArgsConstructor ;
import lombok. Data ;
import lombok. NoArgsConstructor ;
import java. io. Serializable ;
@TableName ( value = "sys_menu" )
@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude ( JsonInclude. Include . NON_NULL )
public class Menu implements Serializable {
@TableId
private Long id;
private String menuName;
private String path;
private String perms;
}
User
package com. cnbai. entity ;
import com. baomidou. mybatisplus. annotation. TableId ;
import com. baomidou. mybatisplus. annotation. TableName ;
import lombok. AllArgsConstructor ;
import lombok. Data ;
import lombok. NoArgsConstructor ;
@TableName ( value = "sys_user" )
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId
private Long id;
private String username;
private String password;
private String roleId;
}
6. 创建业务类
LoginController
package com. cnbai. controller ;
import com. cnbai. entity. User ;
import com. cnbai. service. LoginService ;
import com. cnbai. utils. ResponseResult ;
import org. springframework. security. access. prepost. PreAuthorize ;
import org. springframework. web. bind. annotation. PostMapping ;
import org. springframework. web. bind. annotation. RequestBody ;
import org. springframework. web. bind. annotation. RequestMapping ;
import org. springframework. web. bind. annotation. RestController ;
import javax. annotation. Resource ;
@RestController
public class LoginController {
@Resource
private LoginService loginService;
@PostMapping ( "/user/login" )
public ResponseResult login ( @RequestBody User user) {
return loginService. login ( user) ;
}
@PostMapping ( "/logout" )
public ResponseResult logout ( ) {
return loginService. logout ( ) ;
}
@RequestMapping ( "/hello" )
@PreAuthorize ( "hasAuthority('admin')" )
public String hello ( ) {
return "hello" ;
}
@RequestMapping ( "/test" )
@PreAuthorize ( "hasAuthority('test')" )
public String test ( ) {
return "test" ;
}
@RequestMapping ( "/info" )
@PreAuthorize ( "hasAnyAuthority('test','admin','system:user:list')" )
public String info ( ) {
return "info" ;
}
@RequestMapping ( "/role" )
@PreAuthorize ( "hasRole('system:user:list')" )
public String role ( ) {
return "role" ;
}
@RequestMapping ( "/anyRole" )
@PreAuthorize ( "hasAnyRole('admin','system:user:list')" )
public String anyRole ( ) {
return "anyRole" ;
}
}
LoginService
package com. cnbai. service ;
import com. cnbai. entity. User ;
import com. cnbai. utils. ResponseResult ;
public interface LoginService {
ResponseResult login ( User user) ;
ResponseResult logout ( ) ;
}
LoginServiceImpl
package com. cnbai. service ;
import com. cnbai. entity. LoginUser ;
import com. cnbai. entity. User ;
import com. cnbai. utils. JwtUtils ;
import com. cnbai. utils. ResponseResult ;
import org. springframework. data. redis. core. RedisTemplate ;
import org. springframework. security. authentication. AuthenticationManager ;
import org. springframework. security. authentication. UsernamePasswordAuthenticationToken ;
import org. springframework. security. core. Authentication ;
import org. springframework. security. core. context. SecurityContextHolder ;
import org. springframework. stereotype. Service ;
import javax. annotation. Resource ;
import java. util. HashMap ;
import java. util. Map ;
import java. util. Objects ;
@Service
public class LoginServiceImpl implements LoginService {
@Resource
private AuthenticationManager authenticationManager;
@Resource
private RedisTemplate redisTemplate;
@Override
public ResponseResult login ( User user) {
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken ( user. getUsername ( ) , user. getPassword ( ) ) ;
Authentication authenticate = authenticationManager. authenticate ( authenticationToken) ;
if ( Objects . isNull ( authenticate) ) {
throw new RuntimeException ( "认证失败" ) ;
}
LoginUser loginUser = ( LoginUser ) authenticate. getPrincipal ( ) ;
String userId = loginUser. getUser ( ) . getId ( ) . toString ( ) ;
String jwt = JwtUtils . generateToken ( userId) ;
Map < String , String > map = new HashMap < > ( ) ;
map. put ( "token" , jwt) ;
redisTemplate. opsForValue ( ) . set ( "login:" + userId, loginUser) ;
return new ResponseResult ( 200 , "登录成功" , map) ;
}
@Override
public ResponseResult logout ( ) {
UsernamePasswordAuthenticationToken authentication =
( UsernamePasswordAuthenticationToken ) SecurityContextHolder . getContext ( ) . getAuthentication ( ) ;
LoginUser loginUser = ( LoginUser ) authentication. getPrincipal ( ) ;
Long userId = loginUser. getUser ( ) . getId ( ) ;
redisTemplate. delete ( "login:" + userId) ;
return new ResponseResult ( 200 , "注销成功" ) ;
}
}
UserDetailServiceImpl
package com. cnbai. service ;
import com. baomidou. mybatisplus. core. conditions. query. LambdaQueryWrapper ;
import com. cnbai. dao. MenuDao ;
import com. cnbai. dao. UserDao ;
import com. cnbai. entity. LoginUser ;
import com. cnbai. entity. User ;
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 ;
import javax. annotation. Resource ;
import java. util. List ;
import java. util. Objects ;
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Resource
private UserDao userDao;
@Resource
private MenuDao menuDao;
@Override
public UserDetails loadUserByUsername ( String username) throws UsernameNotFoundException {
LambdaQueryWrapper < User > queryWrapper = new LambdaQueryWrapper < > ( ) ;
queryWrapper. eq ( User :: getUsername , username) ;
User user = userDao. selectOne ( queryWrapper) ;
if ( Objects . isNull ( user) ) {
throw new RuntimeException ( "用户名或者密码错误" ) ;
}
List < String > list = menuDao. selectPermsByUserId ( user. getId ( ) ) ;
return new LoginUser ( user, list) ;
}
}
MenuDao
package com. cnbai. dao ;
import com. baomidou. mybatisplus. core. mapper. BaseMapper ;
import com. cnbai. entity. Menu ;
import org. apache. ibatis. annotations. Mapper ;
import java. util. List ;
@Mapper
public interface MenuDao extends BaseMapper < Menu > {
List < String > selectPermsByUserId ( Long userId) ;
}
UserDao
package com. cnbai. dao ;
import com. baomidou. mybatisplus. core. mapper. BaseMapper ;
import com. cnbai. entity. User ;
import org. apache. ibatis. annotations. Mapper ;
@Mapper
public interface UserDao extends BaseMapper < User > {
}
7. 创建过滤器
AccessDeniedHandlerImpl
package com. cnbai. handler ;
import com. alibaba. fastjson. JSON ;
import com. cnbai. utils. ResponseResult ;
import org. springframework. http. HttpStatus ;
import org. springframework. security. access. AccessDeniedException ;
import org. springframework. security. web. access. AccessDeniedHandler ;
import org. springframework. stereotype. Component ;
import javax. servlet. ServletException ;
import javax. servlet. http. HttpServletRequest ;
import javax. servlet. http. HttpServletResponse ;
import java. io. IOException ;
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle ( HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException , ServletException {
ResponseResult result = new ResponseResult ( HttpStatus . FORBIDDEN . value ( ) , "您的权限不足" ) ;
String json = JSON . toJSONString ( result) ;
System . out. println ( "授权失败:" + json) ;
}
}
AuthenticationEntryPointImpl
package com. cnbai. handler ;
import com. alibaba. fastjson. JSON ;
import com. cnbai. utils. ResponseResult ;
import org. springframework. http. HttpStatus ;
import org. springframework. security. core. AuthenticationException ;
import org. springframework. security. web. AuthenticationEntryPoint ;
import org. springframework. stereotype. Component ;
import javax. servlet. ServletException ;
import javax. servlet. http. HttpServletRequest ;
import javax. servlet. http. HttpServletResponse ;
import java. io. IOException ;
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
public void commence ( HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException , ServletException {
ResponseResult result = new ResponseResult ( HttpStatus . UNAUTHORIZED . value ( ) , "用户认证失败请查询登录" ) ;
String json = JSON . toJSONString ( result) ;
System . out. println ( "认证失败:" + json) ;
}
}
JwtAuthenticationTokenFilter
package com. cnbai. handler ;
import com. cnbai. entity. LoginUser ;
import com. cnbai. utils. JwtUtils ;
import io. jsonwebtoken. Claims ;
import org. springframework. data. redis. core. RedisTemplate ;
import org. springframework. security. authentication. UsernamePasswordAuthenticationToken ;
import org. springframework. security. core. context. SecurityContextHolder ;
import org. springframework. stereotype. Component ;
import org. springframework. util. StringUtils ;
import org. springframework. web. filter. OncePerRequestFilter ;
import javax. annotation. Resource ;
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. Objects ;
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Resource
private RedisTemplate redisTemplate;
@Override
protected void doFilterInternal ( HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException , IOException {
String token = request. getHeader ( "token" ) ;
if ( ! StringUtils . hasText ( token) ) {
filterChain. doFilter ( request, response) ;
return ;
}
String userId;
try {
Claims claims = JwtUtils . parseJWT ( token) ;
userId = claims. getSubject ( ) ;
} catch ( Exception e) {
e. printStackTrace ( ) ;
throw new RuntimeException ( "token非法" ) ;
}
String redisKey = "login:" + userId;
Object user = redisTemplate. opsForValue ( ) . get ( redisKey) ;
if ( Objects . isNull ( user) ) {
throw new RuntimeException ( "用户未登录" ) ;
}
LoginUser loginUser = ( LoginUser ) user;
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken ( loginUser, null , loginUser. getAuthorities ( ) ) ;
SecurityContextHolder . getContext ( ) . setAuthentication ( authenticationToken) ;
filterChain. doFilter ( request, response) ;
}
}
8. 创建工具类
JwtUtils
package com. cnbai. utils ;
import io. jsonwebtoken. Claims ;
import io. jsonwebtoken. Jwts ;
import io. jsonwebtoken. SignatureAlgorithm ;
import java. util. Date ;
public class JwtUtils {
private static final String secretKey = "123456" ;
private static final long time = 86400000 ;
public static String generateToken ( String userId) {
return generateToken ( userId, "admin" ) ;
}
public static String generateToken ( String userId, String role) {
Date now = new Date ( ) ;
return Jwts . builder ( )
. setSubject ( userId)
. setIssuedAt ( now)
. setExpiration ( new Date ( now. getTime ( ) + time) )
. claim ( "role" , role)
. signWith ( SignatureAlgorithm . HS256 , secretKey)
. compact ( ) ;
}
public static Claims parseJWT ( String jwt) {
return Jwts . parser ( )
. setSigningKey ( secretKey)
. parseClaimsJws ( jwt)
. getBody ( ) ;
}
}
ResponseResult
package com. cnbai. utils ;
import lombok. Data ;
import org. springframework. http. HttpStatus ;
import java. io. Serializable ;
@Data
public class ResponseResult < T > implements Serializable {
private int code;
private String msg;
private T data;
public ResponseResult ( int code, String msg) {
this . code = code;
this . msg = msg;
}
public ResponseResult ( int code, String msg, T data) {
this . code = code;
this . msg = msg;
this . data = data;
}
public static ResponseResult success ( ) {
return ResponseResult . success ( HttpStatus . OK . value ( ) , "操作成功" , null ) ;
}
public static ResponseResult success ( String msg) {
return ResponseResult . success ( HttpStatus . OK . value ( ) , msg, null ) ;
}
public static ResponseResult success ( Integer code, String msg, Object data) {
return new ResponseResult ( code, msg, data) ;
}
public static ResponseResult error ( ) {
return ResponseResult . error ( HttpStatus . INTERNAL_SERVER_ERROR . value ( ) , "操作失败" , null ) ;
}
public static ResponseResult error ( String msg) {
return ResponseResult . error ( HttpStatus . INTERNAL_SERVER_ERROR . value ( ) , msg, null ) ;
}
public static ResponseResult error ( Integer code, String msg, Object data) {
return new ResponseResult ( code, msg, data) ;
}
}
9. 创建 Mapper
MenuMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<! DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
< mapper namespace = " com.cnbai.dao.MenuDao" >
< select id = " selectPermsByUserId" resultType = " java.lang.String" >
SELECT
DISTINCT m.perms
FROM
sys_user_role ur
LEFT JOIN sys_role r ON ur.role_id = r.id
LEFT JOIN sys_role_menu rm ON ur.role_id = rm.role_id
LEFT JOIN sys_menu m ON m.id = rm.menu_id
WHERE
ur.user_id = #{userId}
</ select>
</ mapper>
10. 创建启动类
Application
package com. cnbai ;
import org. mybatis. spring. annotation. MapperScan ;
import org. springframework. boot. SpringApplication ;
import org. springframework. boot. autoconfigure. SpringBootApplication ;
@SpringBootApplication
@MapperScan ( "com.cnbai.dao" )
public class Application {
public static void main ( String [ ] args) {
SpringApplication . run ( Application . class , args) ;
}
}