• SpringSecurity框架学习


    1 认识SpringSecurity

    1.1 认证

    设置配置文件

    1. @Configuration
    2. public class SecurityConfig extends WebSecurityConfigurerAdapter {
    3. @Bean
    4. PasswordEncoder passwordEncoder() {
    5. return NoOpPasswordEncoder.getInstance();}
    6. //认证用户账号和密码
    7. @Override
    8. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    9. auth.inMemoryAuthentication()
    10. .withUser("admin")
    11. .password("123456").roles("admin");
    12. }
    13. //静态资源不拦截
    14. @Override
    15. public void configure(WebSecurity web) throws Exception {
    16. web.ignoring().antMatchers("/js/**", "/css/**","/images/**");
    17. }
    18. //表单登录设置
    19. @Override
    20. protected void configure(HttpSecurity http) throws Exception {
    21. http
    22. .authorizeRequests()
    23. .anyRequest().authenticated()
    24. .and()
    25. .formLogin()
    26. .loginPage("/login.html")
    27. .permitAll()
    28. .and()
    29. .csrf()
    30. .disable();
    31. }
    32. }

    在 Spring Security 中,如果我们不做任何配置,默认的登录页面和登录接口的地址都是 /login,也就是说,默认会存在如下两个请求:

    GET http://localhost:8080/login

    POST http://localhost:8080/login

    可以通过 loginProcessingUrl 方法来指定登录接口地址 

     此时我们还需要修改登录页面里边的 action 属性,改为 /doLogin

     设置登录成功重定向:

    • defaultSuccessUrl:只设定一个参数时,指定登录成功的跳转页面为 /index,此时分两种情况:如果你是直接在浏览器中输入的登录地址,登录成功后,就直接跳转到 /index;如果你是在浏览器中输入了其他地址,例如 http://localhost:8080/hello,结果因为没有登录,又重定向到登录页面,此时登录成功后,是来到 /hello 页面。第二个参数如果不设置默认为 false,也就是我们上面的的情况,如果手动设置第二个参数为 true,则 defaultSuccessUrl 的效果和 successForwardUrl 一致。
    • successForwardUrl:不管你是从哪里来的,登录后一律跳转到 successForwardUrl 指定的地址。

    与登录成功相似,登录失败也是有两个方法:

    • failureForwardUrl:是登录失败之后会发生服务端跳转
    • failureUrl:则在登录失败之后,会发生重定向

    1.2 授权

    设置两个账号进行权限实验测试:

    1. @Override
    2. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    3. auth.inMemoryAuthentication()
    4. .withUser("lm").password("123123").roles("root")
    5. .and()
    6. .withUser("admin").password("123456").roles("admin")
    7. .and()
    8. .passwordEncoder(new UserPasswordEncoder());
    9. }

    加密UserPasswordEncoder

    1. public class UserPasswordEncoder implements PasswordEncoder {
    2. @Override
    3. public String encode(CharSequence charSequence) {
    4. return charSequence.toString();
    5. }
    6. @Override
    7. public boolean matches(CharSequence charSequence, String s) {
    8. return s.equals(charSequence.toString());
    9. }
    10. }

    路径权配置

    1. http
    2. .authorizeRequests()
    3. .antMatchers("/lg/admin/**").hasRole("admin")
    4. .antMatchers("/lg/root/**").hasRole("root")
    5. .anyRequest().authenticated()
    6. .and()
    7. ...

    1)「hasAuthority(String)」 判断角色是否具有特定权限

    http.authorizeRequests().antMatchers("/main1.html").hasAuthority("admin")

    2)「hasAnyAuthority(String ...)」 如果用户具备给定权限中某一个,就允许访问

    http.authorizeRequests().antMatchers("/admin/read").hasAnyAuthority("xxx","xxx")

    3)「hasRole(String)」 如果用户具备给定角色就允许访问。否则出现403

    http.authorizeRequests().antMatchers("/admin/read").hasRole("管理员")

    4) 「hasAnyRole(String ...)」 如果用户具备给定角色的任意一个,就允许被访问

    http.authorizeRequests().antMatchers("/guest/read").hasAnyRole("管理员", "访客")

    5) 「hasIpAddress(String)」 请求是指定的IP就运行访问

    http.authorizeRequests().antMatchers("/ip").hasIpAddress("127.0.0.1")

    测试demo

    1. @Controller
    2. @RequestMapping("/lg")
    3. public class HelloControl {
    4. @RequestMapping(value = "/index",method = RequestMethod.GET)
    5. public String userLogin(){
    6. return "index";
    7. }
    8. @RequestMapping(value = "/hello",method = RequestMethod.GET)
    9. @ResponseBody
    10. public String hello(){
    11. return "this is hello";
    12. }
    13. @RequestMapping(value = "/root",method = RequestMethod.GET)
    14. @ResponseBody
    15. public String root(){
    16. return "this is root";
    17. }
    18. @RequestMapping(value = "/admin",method = RequestMethod.GET)
    19. @ResponseBody
    20. public String admin(){
    21. return "this is admin";
    22. }
    23. }

    使用lm账号登录测试一下: 

     ​​​​​1.3 日志

    Spring Boot默认使用LogBack日志系统,如果不需要更改为其他日志系统如Log4j2等,则无需多余的配置,LogBack默认将日志打印到控制台上。

    使用日志功能,只需要在相应类上加上@Slf4j(lambok组件)注解,在对应方法中log.info(),log.error()等就可以输出日志。

     同时可以在application.properties里进行配置,将日志写入本地

    2 SpringSecurity 权限管理设计

    2.1 用户角色权限设计

    1)pom.xml配置

    1. <dependency>
    2. <groupId>org.mybatis.spring.bootgroupId>
    3. <artifactId>mybatis-spring-boot-starterartifactId>
    4. <version>2.1.2version>
    5. dependency>
    6. <dependency>
    7. <groupId>org.projectlombokgroupId>
    8. <artifactId>lombokartifactId>
    9. <optional>trueoptional>
    10. dependency>
    11. <dependency>
    12. <groupId>com.alibabagroupId>
    13. <artifactId>druid-spring-boot-starterartifactId>
    14. <version>1.1.21version>
    15. dependency>
    16. <dependency>
    17. <groupId>mysqlgroupId>
    18. <artifactId>mysql-connector-javaartifactId>
    19. <scope>runtimescope>
    20. dependency>

    2)application.properties配置

    1. spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    2. spring.datasource.url=jdbc:mysql://localhost:3306/springsecurity
    3. spring.datasource.username=root
    4. spring.datasource.password=rootlhc
    5. # 初始化配置
    6. spring.datasource.druid.initial-size=3
    7. # 最小连接数
    8. spring.datasource.druid.min-idle=3
    9. # 最大连接数
    10. spring.datasource.druid.max-active=15
    11. # 获取连接超时时间
    12. spring.datasource.druid.max-wait=5000
    13. # 连接有效性检测时间
    14. spring.datasource.druid.time-between-eviction-runs-millis=90000
    15. # 最大空闲时间
    16. spring.datasource.druid.min-evictable-idle-time-millis=1800000
    17. spring.datasource.druid.test-while-idle=true
    18. spring.datasource.druid.test-on-borrow=false
    19. spring.datasource.druid.test-on-return=false
    20. spring.datasource.druid.validation-query=select 1
    21. # 配置监控统计拦截的filters
    22. spring.datasource.druid.filters=stat
    23. spring.datasource.druid.web-stat-filter.url-pattern=/*
    24. spring.datasource.druid.web-stat-filter.exclusions="*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
    25. spring.datasource.druid.stat-view-servlet.enabled=true
    26. spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
    27. spring.datasource.druid.stat-view-servlet.reset-enable=true
    28. spring.datasource.druid.stat-view-servlet.login-username=admin
    29. spring.datasource.druid.stat-view-servlet.login-password=admin
    30. spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
    31. spring.jackson.time-zone=GMT+8
    32. mybatis.type-aliases-package=com.lhc.lhcprojectdemo.util.entity
    33. mybatis.mapper-locations=classpath:/mappers/*
    34. mybatis.configuration.map-underscore-to-camel-case=true

    3)SysUser设置

    SysUser.java

    1. @Data
    2. @EqualsAndHashCode(callSuper = true)
    3. @NoArgsConstructor
    4. public class SysUser extends BaseEntity{
    5. private static final long serialVersionUID = -6525908145032868837L;
    6. private Integer userId;
    7. private Integer deptId;
    8. private String userName;
    9. private String password;
    10. private String nickName;
    11. private String phone;
    12. private String email;
    13. private Integer status;
    14. public interface Status {
    15. int LOCKED = 0;
    16. int VALID = 1;
    17. }
    18. }

    UserDao.java

    1. @Mapper
    2. public interface UserDao {
    3. /**
    4. * 分页返回所有用户
    5. */
    6. List getAllUserByPage(@Param("startPosition")Integer startPosition, @Param("limit")Integer limit);
    7. }

    UserService.java

    1. public interface UserService {
    2. ResultUtil getAllUsersByPage(Integer startPosition, Integer limit);
    3. }

    UserServiceImpl.java

    1. @Service
    2. public class UserServiceImpl implements UserService {
    3. @Autowired
    4. private UserDao userDao;
    5. @Override
    6. public ResultUtil getAllUsersByPage(Integer startPosition, Integer limit) {
    7. return ResultUtil.ok().data(userDao.getAllUserByPage(startPosition,limit)).code(ResultCode.TABLE_SUCCESS);
    8. }
    9. }

    UseControl.java

    1. @Controller
    2. @RequestMapping("/api/user")
    3. public class UserControl {
    4. @Autowired
    5. private UserService userService;
    6. @GetMapping("/index")
    7. @ResponseBody
    8. public ResultUtil index(PageTableRequest pageTableRequest){
    9. pageTableRequest.countOffset();
    10. return userService.getAllUsersByPage(pageTableRequest.getOffset(),pageTableRequest.getLimit());
    11. }
    12. }

    UserMapper.xml

    1. "1.0" encoding="UTF-8" ?>
    2. mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    3. <mapper namespace="com.lhc.lhcprojectdemo.dao.UserDao">
    4. <select id="getAllUserByPage" resultType="com.lhc.lhcprojectdemo.util.entity.SysUser">
    5. SELECT u.user_id,u.dept_id,u.user_name,u.password,u.nick_name,u.phone,u.email,u.status,u.create_time,u.update_time
    6. FROM sys_user u
    7. ORDER BY u.user_id
    8. select>
    9. mapper>

    4)测试 

    5)设置角色和权限

    sys_role

    sys_menu(可以简单设置sys_permit)

      然后关联user- role,menu-role(permit-role)

     2.2 security权限控制

    1)给API添加权限

     2)添加security配置注解

    3)认证授权

    1. @Autowired
    2. private UserDetailsService userDetailsService;
    3. @Bean
    4. public PasswordEncoder passwordEncoder(){
    5. return new BCryptPasswordEncoder();
    6. }
    7. @Override
    8. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    9. auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    10. }

    4)自定义用户信息

    1. @Data
    2. @ToString
    3. public class JwtUserDto implements UserDetails {
    4. /**
    5. * 用户数据
    6. */
    7. private SysUser myUser;
    8. //private List roleInfo;
    9. /**
    10. * 用户权限的集合
    11. */
    12. @JsonIgnore
    13. private List authorities;
    14. public List getRoles() {
    15. return authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
    16. }
    17. /**
    18. * 加密后的密码
    19. * @return
    20. */
    21. public String getPassword() {
    22. return myUser.getPassword();
    23. }
    24. /**
    25. * 用户名
    26. * @return
    27. */
    28. public String getUsername() {
    29. return myUser.getUserName();
    30. }
    31. /**
    32. * 是否过期
    33. * @return
    34. */
    35. public boolean isAccountNonExpired() {
    36. return true;
    37. }
    38. /**
    39. * 是否锁定
    40. * @return
    41. */
    42. public boolean isAccountNonLocked() {
    43. return true;
    44. }
    45. /**
    46. * 凭证是否过期
    47. * @return
    48. */
    49. public boolean isCredentialsNonExpired() {
    50. return true;
    51. }
    52. /**
    53. * 是否可用
    54. * @return
    55. */
    56. public boolean isEnabled() {
    57. return myUser.getStatus() == 1 ? true : false;
    58. }
    59. public JwtUserDto(SysUser sysUser, List authorities) {
    60. this.myUser = sysUser;
    61. this.authorities = authorities;
    62. }
    63. }

    5)自定义一个UserDetailsServiceImpl实现UserDetailsService

    1. @Service
    2. @Slf4j
    3. public class UserDetailsServiceImpl implements UserDetailsService {
    4. @Autowired
    5. private UserService userService;
    6. @Autowired
    7. private RoleService roleService;
    8. @Autowired
    9. private RoleUserService roleUserService;
    10. @Autowired
    11. private MenuDao menuDao;
    12. @Override
    13. public JwtUserDto loadUserByUsername(String userName) throws UsernameNotFoundException {
    14. SysUser user = userService.getUserByName(userName);//根据用户名获取用户
    15. if (user == null ){
    16. throw new UsernameNotFoundException("用户名不存在");//这个异常一定要抛
    17. }else if (user.getStatus().equals(SysUser.Status.LOCKED)) {
    18. throw new LockedException("用户被锁定,请联系管理员");
    19. }
    20. List grantedAuthorities = new ArrayList<>();
    21. //菜单表中获取信息
    22. List list = menuDao.listByUserId(user.getUserId());
    23. List collect = list.stream().map(MenuIndexDto::getPermission).collect(Collectors.toList());
    24. for (String authority : collect){
    25. if (!("").equals(authority) & authority !=null){
    26. GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(authority);
    27. //获取到user:list等具体权限
    28. grantedAuthorities.add(grantedAuthority);
    29. }
    30. }
    31. JwtUserDto loginUser =new JwtUserDto(user,grantedAuthorities);
    32. return loginUser;
    33. }
    34. }

    2.3 引入验证码

    1)配置pom文件

    1. <dependency>
    2. <groupId>com.github.whvcsegroupId>
    3. <artifactId>easy-captchaartifactId>
    4. <version>1.6.2version>
    5. dependency>

     2)准备验证码生成control

    1. @Controller
    2. public class CaptchaController {
    3. @RequestMapping("/captcha")
    4. public void captcha(HttpServletRequest request, HttpServletResponse response) throws Exception {
    5. CaptchaUtil.out(120, 45, 4, request, response);
    6. }
    7. }

    3)login.html登录界面配置验证码

    1. <div>
    2. <input id="captcha" name="captcha" size="6" style="width:150px;height:40px" placeholder="验 证 码:" type="text">
    3. <img src="/captcha" width="130px" height="40px" onclick="this.src=this.src+'?'+Math.random()" title="点击刷新"/>
    4. div>

    4)验证码校验

    1. @Component
    2. public class VerifyCodeFilter extends OncePerRequestFilter {
    3. private String defaultFilterProcessUrl = "/dologin";
    4. private String method = "POST";
    5. @Override
    6. protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
    7. if (method.equalsIgnoreCase(request.getMethod()) && defaultFilterProcessUrl.equals(request.getServletPath())) {
    8. // 登录请求校验验证码,非登录请求不用校验
    9. HttpSession session = request.getSession();
    10. String requestCaptcha = request.getParameter("captcha");
    11. //验证码的信息存放在seesion种,具体看EasyCaptcha官方解释
    12. String genCaptcha = (String) request.getSession().getAttribute("captcha");
    13. response.setContentType("application/json;charset=UTF-8");
    14. if (requestCaptcha.isEmpty()){
    15. //删除缓存里的验证码信息
    16. session.removeAttribute("captcha");
    17. response.getWriter().write(JSON.toJSONString(ResultUtil.error().message("验证码不能为空!")));
    18. return;
    19. }
    20. if (genCaptcha.isEmpty()){
    21. response.getWriter().write(JSON.toJSONString(ResultUtil.error().message("验证码已失效!")));
    22. return;
    23. }
    24. if (!genCaptcha.equalsIgnoreCase(requestCaptcha)){
    25. session.removeAttribute("captcha");
    26. response.getWriter().write(JSON.toJSONString(ResultUtil.error().message("验证码错误!")));
    27. return;
    28. }
    29. }
    30. chain.doFilter(request, response);
    31. }
    32. }

    5)security配置

  • 相关阅读:
    「五度情报站」网罗全量企业情报,找客户、查竞品、寻商机!
    【数据结构】一种令人愉悦的链表——带头双向循环链表(代码实现)
    CAN - 基础
    “开源 vs. 闭源:大模型的未来发展趋势预测“——探讨大模型未来的发展方向
    mysql 主从复制
    聚精品,通全球 2024中国(杭州)国际电商物流包装产业展览会四月隆重开幕
    HDLBits-Edgedetect
    java毕业设计企业资产管理系统mybatis+源码+调试部署+系统+数据库+lw
    决策中心:构建企业长期战略竞争力
    ASP.NET Core - 依赖注入(一)
  • 原文地址:https://blog.csdn.net/xlsj228/article/details/121124366