1、导入依赖
<dependencies>
//导入SpringSecurity
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
//导入SpringBoo-tWeb
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
//导入mysql
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.21version>
dependency>
//整合mybatis-spring
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.3.0version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-testartifactId>
<scope>testscope>
dependency>
dependencies>
2、使用MybatisPageHelperPro逆向生成实体类和接口、接口mapper
3、实体类实现UserDetails接口重写里面的方法
@Data
public class UserBean implements UserDetails {
/**
* 编号
*/
private Integer userId;
/**
* 登陆名
*/
private String username;
...........
private List<SimpleGrantedAuthority> permission=new ArrayList<>();
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return permission;
}
/**
* 密码是否过期
* @return
*/
@Override
public boolean isCredentialsNonExpired() {
return credentialsNoExpired.equals(1);
}
/**
* 账户是否被禁用
* @return
*/
@Override
public boolean isEnabled() {
return enabled.equals(1);
}
}
/*
* 自定义前端请求统一响应格式
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ResponseData {
private Integer code=200;
private String msg="响应成功";
private Object result;
public ResponseData(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public ResponseData(Object result) {
this.result = result;
}
}
/**
*
*/
public class ResponseJsonUtil {
/**
* 统一在响应流中输入json字符串
* @param response
* @param data
*/
public static void response(HttpServletResponse response, ResponseData data){
ObjectMapper mapper=new ObjectMapper();
try {
String jsonStr=mapper.writeValueAsString(data);
response.setCharacterEncoding("UTF-8");
response.setContentType("text/json;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write(jsonStr);
writer.flush();
writer.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
4、创建ServiceImpl类实现UserDetailsService接口重写loadUserByUsername方法
@Service
public class UserServiceImpl implements UserService, UserDetailsService {
@Autowired
private UserBeanMapper userBeanMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根据用户名从数据库获取密码
UserBean user = userBeanMapper.selectByUserName(username);
if(null==user){
throw new UsernameNotFoundException("用户名不存在");
}
//根据用户id从数据库获取用户的权限
List<String> pers = userBeanMapper.selectPermissionCodeByUserId(user.getUserId());
//将获取到的用户权限一个个放进List中
for(String per : pers){
user.getPermission().add(new SimpleGrantedAuthority(per));
}
return user;
}
}
5、创建自定义Security配置类继承WebSecurityConfigurerAdapter抽象类
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)//开启方法级别的权限认证 默认是false
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserServiceImpl userService;
/**
* 配置用户信息,模拟内存用户数据
*
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//根据传入的自定义 UserDetailsService 添加身份验证。
// 然后它返回一个DaoAuthenticationConfigurer对象以允许自定义身份验证
auth.userDetailsService(userService);
}
@Autowired//注入身份认证失败处理器对象
private AppAuthFailHandler appAuthFailHandler;
@Autowired//注入身份认证成功处理器对象
private AppAuthSuccessHandler appAuthSuccessHandler;
@Autowired//注入访问拒绝处理器对象
private AppAuthDenyHander appAuthDenyHander;
@Autowired//注入退出登陆处理器对象
private AppLogoutHandler appLogoutHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
//身份验证
http.formLogin() //返回一个基于表单的身份验证对象
.successHandler(appAuthSuccessHandler)//登陆成功后执行的处理器
.failureHandler(appAuthFailHandler);//登陆失败后执行的处理器
//无权访问
http.exceptionHandling().accessDeniedHandler(appAuthDenyHander);
//退出登陆处理器
http.logout().addLogoutHandler(appLogoutHandler);
//配置路径拦截 的url的匹配规则
http.authorizeRequests()
//放行不需要权限的页面
.mvcMatchers("/user/u1").anonymous()
//任何路径要求必须认证之后才能访问
.anyRequest().authenticated();
}
/*
* 从 Spring5 开始,强制要求密码要加密
* 如果非不想加密,可以使用一个过期的 PasswordEncoder 的实例 NoOpPasswordEncoder,
* 但是不建议这么做,毕竟不安全。
*
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
6、自定义访问拒绝处理器,认证成功处理器、认证失败处理器,登出处理器,需要注入自定义Security配置类,交给Security处理
/*
* 自定义访问拒绝处理器,实现AccessDeniedHandler接口
*/
@Component
public class AppAuthDenyHander implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
ResponseJsonUtil.response(response,new ResponseData(-1,"403"));
}
}
/*
* 身份认证成功处理器
*/
@Component
public class AppAuthSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
ResponseJsonUtil.response(response,new ResponseData(200,"登录成功",authentication));
}
}
/**
* 登录失败的处理器
*/
@Component
public class AppAuthFailHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
ResponseData result=new ResponseData(-1,"访问失败");
if (exception instanceof BadCredentialsException){
result.setMsg("密码错误");
}else if (exception instanceof UsernameNotFoundException){
result.setMsg("用户名不存在");
}else if(exception instanceof DisabledException){
result.setMsg("账户被禁用");
}else if(exception instanceof AccountExpiredException){
result.setMsg("账户过期");
}else if(exception instanceof CredentialsExpiredException){
result.setMsg("密码过期");
}else if(exception instanceof LockedException){
result.setMsg("账户被锁定");
}else {
result.setMsg("未知错误");
}
ResponseJsonUtil.response(response,result);
}
}
/**
* 登出的处理器
*/
@Component
public class AppLogoutHandler implements LogoutHandler {
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
ResponseJsonUtil.response(response,new ResponseData(200,"退出成功",authentication));
}
}
7、 编写application.yml,配置数据源和mybatis
spring:
datasource:
password: root
username: root
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.6.130:3306/security?useUnicode=true&useSSL=false&characterEncoding=UTF8&serverTimezone=UTC
mybatis:
mapper-locations: classpath*:mybatis/mapper/*.xml
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
8、编写Controller测试
@RestController
@RequestMapping("user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("add")
@PreAuthorize("hasAnyAuthority('user:save')")//拥有save权限的用户可以访问
public void add(HttpServletResponse response){
ResponseData all = userService.findAll();
ResponseJsonUtil.response(response,all);
}
}