SpringSecurity框架学习

1 认识SpringSecurity

1.1 认证

设置配置文件

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

 	 @Bean
    PasswordEncoder passwordEncoder() {
    	return NoOpPasswordEncoder.getInstance();}
    
    //认证用户账号和密码
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("admin")
                .password("123456").roles("admin");
    }

    //静态资源不拦截
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/js/**", "/css/**","/images/**");
}

    //表单登录设置
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login.html")
                .permitAll()
                .and()
            .csrf()
                .disable();
    }
}

在 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 授权

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

 @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("lm").password("123123").roles("root")
                .and()
                .withUser("admin").password("123456").roles("admin")
                .and()
                .passwordEncoder(new UserPasswordEncoder());
    }

加密UserPasswordEncoder

public class UserPasswordEncoder implements PasswordEncoder {

    @Override
    public String encode(CharSequence charSequence) {
        return charSequence.toString();
    }

    @Override
    public boolean matches(CharSequence charSequence, String s) {
        return s.equals(charSequence.toString());
    }
}

路径权配置

http
    .authorizeRequests()
        .antMatchers("/lg/admin/**").hasRole("admin")
        .antMatchers("/lg/root/**").hasRole("root")
        .anyRequest().authenticated()
        .and()
        ...

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

@Controller
@RequestMapping("/lg")
public class HelloControl {

    @RequestMapping(value = "/index",method = RequestMethod.GET)
    public String userLogin(){
        return "index";
    }

    @RequestMapping(value = "/hello",method = RequestMethod.GET)
    @ResponseBody
    public String hello(){
        return "this is hello";
    }

    @RequestMapping(value = "/root",method = RequestMethod.GET)
    @ResponseBody
    public String root(){
        return "this is root";
    }

    @RequestMapping(value = "/admin",method = RequestMethod.GET)
    @ResponseBody
    public String admin(){
        return "this is admin";
    }
}

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

 ​​​​​1.3 日志

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

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

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

2 SpringSecurity 权限管理设计

2.1 用户角色权限设计

1)pom.xml配置

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.2</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--druid连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.21</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

2)application.properties配置

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springsecurity
spring.datasource.username=root
spring.datasource.password=rootlhc
# 初始化配置
spring.datasource.druid.initial-size=3
# 最小连接数
spring.datasource.druid.min-idle=3
# 最大连接数
spring.datasource.druid.max-active=15
# 获取连接超时时间
spring.datasource.druid.max-wait=5000
# 连接有效性检测时间
spring.datasource.druid.time-between-eviction-runs-millis=90000
# 最大空闲时间
spring.datasource.druid.min-evictable-idle-time-millis=1800000
spring.datasource.druid.test-while-idle=true
spring.datasource.druid.test-on-borrow=false
spring.datasource.druid.test-on-return=false
spring.datasource.druid.validation-query=select 1
# 配置监控统计拦截的filters
spring.datasource.druid.filters=stat
spring.datasource.druid.web-stat-filter.url-pattern=/*
spring.datasource.druid.web-stat-filter.exclusions="*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
spring.datasource.druid.stat-view-servlet.enabled=true
spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
spring.datasource.druid.stat-view-servlet.reset-enable=true
spring.datasource.druid.stat-view-servlet.login-username=admin
spring.datasource.druid.stat-view-servlet.login-password=admin
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8

mybatis.type-aliases-package=com.lhc.lhcprojectdemo.util.entity
mybatis.mapper-locations=classpath:/mappers/*
mybatis.configuration.map-underscore-to-camel-case=true

3)SysUser设置

SysUser.java

@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
public class SysUser extends BaseEntity{
    private static final long serialVersionUID = -6525908145032868837L;

    private Integer userId;

    private Integer deptId;

    private String userName;

    private String password;

    private String nickName;

    private String phone;

    private String email;

    private Integer status;

    public interface Status {
        int LOCKED = 0;
        int VALID = 1;
    }
}

UserDao.java

@Mapper
public interface UserDao {
    /**
     * 分页返回所有用户
     */
    List<SysUser> getAllUserByPage(@Param("startPosition")Integer startPosition, @Param("limit")Integer limit);
    
}

UserService.java

public interface UserService {
    ResultUtil<SysUser> getAllUsersByPage(Integer startPosition, Integer limit);
}

UserServiceImpl.java

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;

    @Override
    public ResultUtil<SysUser> getAllUsersByPage(Integer startPosition, Integer limit) {
        return ResultUtil.ok().data(userDao.getAllUserByPage(startPosition,limit)).code(ResultCode.TABLE_SUCCESS);
    }
}

UseControl.java

@Controller
@RequestMapping("/api/user")
public class UserControl {
    @Autowired
    private UserService userService;

    @GetMapping("/index")
    @ResponseBody
    public ResultUtil<SysUser> index(PageTableRequest pageTableRequest){
        pageTableRequest.countOffset();
        return userService.getAllUsersByPage(pageTableRequest.getOffset(),pageTableRequest.getLimit());
    }
}

UserMapper.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.lhc.lhcprojectdemo.dao.UserDao">

    <select id="getAllUserByPage" resultType="com.lhc.lhcprojectdemo.util.entity.SysUser">
        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
        FROM sys_user u
        ORDER BY u.user_id
    </select>

</mapper>

4)测试 

5)设置角色和权限

sys_role

sys_menu(可以简单设置sys_permit)

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

 2.2 security权限控制

1)给API添加权限

 2)添加security配置注解

3)认证授权

@Autowired
private UserDetailsService userDetailsService;

@Bean
public PasswordEncoder passwordEncoder(){
    return new BCryptPasswordEncoder();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}

4)自定义用户信息

@Data
@ToString
public class JwtUserDto implements UserDetails {
    /**
     * 用户数据
     */
    private SysUser myUser;

    //private List<SysRole> roleInfo;
    /**
     * 用户权限的集合
     */
    @JsonIgnore
    private List<GrantedAuthority> authorities;

    public List<String> getRoles() {
        return authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
    }


    /**
     * 加密后的密码
     * @return
     */
    public String getPassword() {
        return myUser.getPassword();
    }


    /**
     * 用户名
     * @return
     */
    public String getUsername() {
        return myUser.getUserName();
    }


    /**
     * 是否过期
     * @return
     */
    public boolean isAccountNonExpired() {
        return true;
    }


    /**
     * 是否锁定
     * @return
     */
    public boolean isAccountNonLocked() {
        return true;
    }


    /**
     * 凭证是否过期
     * @return
     */
    public boolean isCredentialsNonExpired() {
        return true;
    }


    /**
     * 是否可用
     * @return
     */
    public boolean isEnabled() {
        return myUser.getStatus() == 1 ? true : false;
    }


    public JwtUserDto(SysUser sysUser, List<GrantedAuthority> authorities) {
        this.myUser = sysUser;
        this.authorities = authorities;
    }
}

5)自定义一个UserDetailsServiceImpl实现UserDetailsService

@Service
@Slf4j
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserService userService;
    @Autowired
    private RoleService roleService;
    @Autowired
    private RoleUserService roleUserService;
    @Autowired
    private MenuDao menuDao;

    @Override
    public JwtUserDto loadUserByUsername(String userName) throws UsernameNotFoundException {
        SysUser user = userService.getUserByName(userName);//根据用户名获取用户
        if (user == null ){
            throw new UsernameNotFoundException("用户名不存在");//这个异常一定要抛
        }else if (user.getStatus().equals(SysUser.Status.LOCKED)) {
            throw new LockedException("用户被锁定,请联系管理员");
        }
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        //菜单表中获取信息
        List<MenuIndexDto> list = menuDao.listByUserId(user.getUserId());
        
        List<String> collect = list.stream().map(MenuIndexDto::getPermission).collect(Collectors.toList());
        for (String authority : collect){
            if (!("").equals(authority) & authority !=null){
                GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(authority);
                //获取到user:list等具体权限
                grantedAuthorities.add(grantedAuthority);
            }
        }
        JwtUserDto loginUser =new JwtUserDto(user,grantedAuthorities);
        return loginUser;
    }
}

2.3 引入验证码

1)配置pom文件

 <!--验证码-->
<dependency>
    <groupId>com.github.whvcse</groupId>
    <artifactId>easy-captcha</artifactId>
    <version>1.6.2</version>
</dependency>

 2)准备验证码生成control

@Controller
public class CaptchaController {

    @RequestMapping("/captcha")
    public void captcha(HttpServletRequest request, HttpServletResponse response) throws Exception {
        CaptchaUtil.out(120, 45, 4, request, response);
    }
}

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

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

4)验证码校验

@Component
public class VerifyCodeFilter extends OncePerRequestFilter {
    private String defaultFilterProcessUrl = "/dologin";
    private String method = "POST";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        if (method.equalsIgnoreCase(request.getMethod()) && defaultFilterProcessUrl.equals(request.getServletPath())) {
            // 登录请求校验验证码,非登录请求不用校验
            HttpSession session = request.getSession();
            String requestCaptcha = request.getParameter("captcha");
            //验证码的信息存放在seesion种,具体看EasyCaptcha官方解释
            String genCaptcha = (String) request.getSession().getAttribute("captcha");
            response.setContentType("application/json;charset=UTF-8");
            if (requestCaptcha.isEmpty()){
                //删除缓存里的验证码信息
                session.removeAttribute("captcha");
                response.getWriter().write(JSON.toJSONString(ResultUtil.error().message("验证码不能为空!")));
                return;
            }
            if (genCaptcha.isEmpty()){
                response.getWriter().write(JSON.toJSONString(ResultUtil.error().message("验证码已失效!")));
                return;
            }
            if (!genCaptcha.equalsIgnoreCase(requestCaptcha)){
                session.removeAttribute("captcha");
                response.getWriter().write(JSON.toJSONString(ResultUtil.error().message("验证码错误!")));
                return;
            }
        }
        chain.doFilter(request, response);
    }
}

5)security配置

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值