SpringBoot配置SpringSecurity
很多人新人在写springboot项目的时候想加权限控制,但是由于springsecurity默认全盘接管,不敢放开security的jar包,这里来一期详细的讲解,解决你的权限控制问题,希望能作为你的参考文档
一、准备
-
导入jar包
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
-
相关基础知识
403状态码为权限不足,说明该此访问已被security拒绝 如果没有配置指定登录页面,security有默认页面,security的版本越高,登录界面做的越好看(不是每个版本都有变动)
二、配置类编写
这写配置也可以直接写到yaml里面,但是个人开发我常用的是写配置类
在写配置类之前一定要注意一点,如果你使用了security进行权限控制,首先检查springmvc的配置中有没有设置对静态资源或者方法访问的拦截器,有的话请避免权限冲突,或者统一权限管理,不然security没有拦截的资源会被springmvc拦截,使security的配置失效
-
SecurityConfig
//这里我直接把导包情况也写进来了,以免出现导包问题 //cn.wangjing921.开头的包都是自己写的类,最后会展示 import cn.wangjing921.service.security.LoginService; //自己写的权限赋予、验证类 import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 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.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @Configuration //配置类注解 @EnableWebSecurity //接管security配置 @EnableGlobalMethodSecurity(prePostEnabled = true)//全局方法拦截 public class SecurityConfig extends WebSecurityConfigurerAdapter { //继承WebSecurityConfigurerAdapter @Autowired LoginService userDetailsService;//自己写的权限赋予、验证类,稍后会介绍 @Autowired BCryptPasswordEncoder bCryptPasswordEncoder;//加密类,这个是security官方推荐的加密包 /** * 授权 * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { //请求授权的规则,设置必须登录 http.authorizeRequests()//支持链式编程 .antMatchers("/toLogin").permitAll()//对/toLogin全面放行 .antMatchers("/**").hasAnyRole("普通用户","管理员","VIP")//对指定的路径进行权限监控,只有用有对应的权限才能访问 ; //没有权限默认去登录页面,需要开启登录的页面 http.formLogin() // .loginPage("/login.html")//设置登录页面 .loginProcessingUrl("/toLogin")//自定义登录接口 .defaultSuccessUrl("/main")//指定登录成功跳转页面 ; //开启注销 http.logout() .logoutSuccessUrl("/toLogin");//注销成功跳转那个页面 //防止网站工具 : get post http.csrf().disable();//关闭csrf跨域,默认是开启的 //开启记住我功能 cookie 默认保存两周 http.rememberMe() .rememberMeParameter("remember")//设置记住我的name的别名 ; } /** * 静态资源放行 * @param web * @throws Exception */ @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/static/**"); } /** * 注入加密类 * @return */ @Bean public BCryptPasswordEncoder bCryptPasswordEncoder(){ return new BCryptPasswordEncoder();//装配加密类 //被security接管的登录必须要被加密,所以当你进行用户注册操作的时候在数据库中看到的密码是一串加密后的文字 } /** * 认证 * @param auth * @throws Exception */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService)//配置认证服务 .passwordEncoder(bCryptPasswordEncoder);//配置解密方式 } }
-
用户认证服务
//cn.wangjing921.开头的包都是自己写的类,最后会展示 import cn.wangjing921.custenum.domainTypeEnum.LVEnum;//权限枚举 import cn.wangjing921.domain.po.Luser;//用户实体类 import cn.wangjing921.mapper.LuserMapper;//mapper import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.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.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; @Service @Slf4j//日志 public class LoginService implements UserDetailsService { //实现UserDetailsService接口,重写其中loadUserByUsername方法 @Autowired LuserMapper luserMapper;//调用数据库的mapper @Autowired BCryptPasswordEncoder bCryptPasswordEncoder;//加密类 /** * 登录 * @param username 账号 * @return * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{ Luser byUsername =null; try { byUsername=luserMapper.selectByUsername(username);//执行查询方法,获取登录对象 } catch (Exception e) { log.error("用户不存在",e); } //这个user是spring-security的类!!!,第一参数:用户名,二:密码,三~六:查看中文源码,七:权限名列表 User user=new User(byUsername.getUsername(),byUsername.getPassword(),byUsername.getLive().value()==1?true:false,true,true,true,getAuthority(byUsername.getLV())); return user; } /** * 获取role权限列表 这个需要自己设计 * @param roles 权限枚举 * @return 权限集合 */ //作用就是返回一个List集合,集合中装入的是角色描述(获取登录用户拥有的权限) public List<SimpleGrantedAuthority> getAuthority(LVEnum roles) { List<SimpleGrantedAuthority> list = new ArrayList<>();//SimpleGrantedAuthority是权限类型| //以下判断都是个人业务需求,这里只为了方便展示用法 if (LVEnum.ORDINARY.getValue().equals(roles.getValue())){ list.add(new SimpleGrantedAuthority("ROLE_" + LVEnum.ORDINARY.getTitle()));//权限字符串必须以"ROLE_"开头 }else if (LVEnum.VIP.getValue().equals(roles.getValue())){ list.add(new SimpleGrantedAuthority("ROLE_" + LVEnum.VIP.getTitle())); }else if (LVEnum.ADMIN.getValue().equals(roles.getValue())){ list.add(new SimpleGrantedAuthority("ROLE_" + LVEnum.ADMIN.getTitle())); }else if (LVEnum.LOSER.getValue().equals(roles.getValue())){ list.add(new SimpleGrantedAuthority("ROLE_"+ LVEnum.LOSER.getTitle())); } return list; } }
-
用户注册业务
//照搬自己的代码,无关请略过,这里只做展示 @Service @Slf4j @Transactional(rollbackFor=Exception.class) public class LuserServiceImpl implements LuserService { @Autowired LuserMapper luserMapper; @Autowired BCryptPasswordEncoder bCryptPasswordEncoder; @Override public String registerLuser(Luser luser) { //返回值类型后期并不是String,只是初步设想 log.info("执行账号注册------加密密码"); luser.setPassword(bCryptPasswordEncoder.encode(luser.getPassword())); //注意这里要给用户密码加密,必须使用与security配置中相同的加密方法 try { log.info("执行账号注册------插入数据库"); luserMapper.insertSelective(luser); log.info("执行账号注册------完成"); return "成功+msg"; } catch (Exception e) { log.error("执行账号注册------插入失败",e); } return "失败+msg"; } }
-
其他相关代码(已掌握可不必往下看了)
用户实体类
/** * 用户表 * @author afflatus */ @Data public class Luser implements Serializable { //只展示了相关字段 private static final long serialVersionUID = 5903901908871919096L; /** * 主键不展示 */ private Integer luserid; /** * 用户登录用 */ private String username; /** * 密码 */ private String password; /** * 手机号 */ private String phone; /** * 激活/开启状态(0未激活1正常2禁用) */ private UserStateEnum live; /** * 用户类型 */ private LVEnum LV; }
实体类使用的枚举
/** * 账号描述,用户类型 * @author afflatus */ public enum LVEnum implements IBaseEnumInterface<Integer> { ORDINARY(0,"普通用户"), ADMIN(1,"管理员"), VIP(2,"VIP"), LOSER(3,"低信誉用户"); private final Integer value; private final String title; private LVEnum(Integer value, String title) { this.value = value; this.title = title; } @JsonValue public Integer getValue() { return value; } public String getTitle() { return title; } @Override public String toString() { return this.title; } @Override public Integer value() { return value; } @Override public String title() { return title; } /** * 通过value获取title * @param value * @return */ @JsonCreator public static String wonTitle(@JsonProperty("value")Integer value) { for (LVEnum ot : values()) { if (ot.getValue().equals(value)) { return ot.getTitle(); } } return null; } } /** * 用户激活状态 * @author afflatus */ public enum UserStateEnum implements IBaseEnumInterface<Integer> { UNACTIVATED(0,"未激活"), LIVE(1,"正常"), DIE(2,"黑名单"); private final Integer value; private final String title; private UserStateEnum(Integer value, String title) { this.value = value; this.title = title; } @JsonValue public Integer getValue() { return value; } public String getTitle() { return title; } @Override public String toString() { return this.title; } @Override public Integer value() { return value; } @Override public String title() { return title; } /** * 通过value获取title * @param value * @return */ @JsonCreator public static String wonTitle(Integer value) { for (UserStateEnum ot : values()) { if (ot.getValue().equals(value)) { return ot.getTitle(); } } return null; } }
结语
不为了多么深层的掌握,只为了感受成功的快乐 ——程序员的快乐