2、spring security中,安全配置通过继承
WebSecurityConfigurerAdapter
来配置@Configuration public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter{protected void configure(HttpSecurity http) throws Exception {//做大量的配置//认证配置//授权配置 // 例如:http.authorizeRequests()//访问"/"和"/home"路径的请求都允许.antMatchers("/", "/home","/staff","/staff/*").permitAll()//而其他的请求都需要认证.anyRequest().authenticated().and()//修改Spring Security默认的登陆界面.formLogin().loginPage("/login").permitAll().and().logout().permitAll();}
3、添加认证校验 (authentication)
创建数据库,创建一个用户库,用户名和密码
创建访问数据库的接口
public interface UserMapper {User selectByUserName(String userName);List<String> selectAllRoleByUserId(Integer userId);List<String> selectPermissionsByUserId(Integer userId);}
创建访问数据库的userMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.demo.mapper.UserMapper"><select id="selectByUserName" resultType="com.demo.entity.User">select id,userName,passwordfrom t_user where username = #{userName}</select><select id="selectAllRoleByUserId" resultType="String">select r.name as name from t_role_user u,t_role r where r.id = u.rid and u.uid=#{userId};</select><select id="selectPermissionsByUserId" resultType="String">SELECT permission FROM t_role r,t_role_user u,t_role_menu rm,t_menu mwhere r.id = u.uid and rm.mid = m.id and u.rid =rm.rid and u.uid=#{userId}</select> </mapper>
新建一个类,实现spring security提供的接口org.springframework.security.core.userdetails.UserDetailsService
这样就会调用该类的方法去访问数据库认证了。这一步中会返回一个User对象,该对象是SpringSecurity里定义的User对象
需要将用户的权限和角色用逗号分割后组装在一起。
@Service public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate PasswordEncoder passwordEncoder;@Autowiredprivate UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {com.demo.entity.User user = userMapper.selectByUserName(s);if (user == null) {throw new UsernameNotFoundException("用户不存在");}List<String> roles = userMapper.selectAllRoleByUserId(user.getId());List<String> permissions = userMapper.selectPermissionsByUserId(user.getId());StringBuilder sb = new StringBuilder();for (String role : roles) {sb.append("ROLE_" + role + ",");}for (String permission : permissions) {sb.append(permission + ",");}String rolepermission = sb.substring(0, sb.length() - 1);UserDetails userDetails = new User(user.getUserName(), user.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList(rolepermission));return userDetails;} }
注意,
认证的步骤,是先获取用户信息,再校验用户密码的认证过程。
在上步骤中需要使用到一个passwordEncoder编码器,来生成加密密码明文生成密文。
通过java类来配置一个passwordEncoder
@Configuration public class SecurityConfig {@Beanprotected PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();} }
4、添加配置,用于完成认证流程管理
登录的界面:/showLogin
登录逻辑入口 /login
登录成功地址 /showMain
登录失败地址 /showFail
.usernameParameter("myusername")
以及.passwordParameter("mypassword")
定义了登录页面中的表单名称是myusername
和mypassword
UsernamePasswordAuthenticationFilter :对/login 的 POST 请求做拦截,校验表单中用户名,密码。
通过源码不难发现:一旦我们使用SpringSecurity来作为认证框架,我们要写自己的登录的接口的话,一定要用post请求,并且用户名和密码是固定的,只能用username和password来作为参数名,因为这是SpringSecurity默认的(如果执意要改,可以通过配置文件进行改)。
public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter{protected void configure(HttpSecurity http) throws Exception {http.formLogin()//未登录时的地址.loginPage("/showLogin")//处理登录请求的url.loginProcessingUrl("/login")//.successForwardUrl("/showMain")//认证成功后跳转的地址.successHandler(new AuthenticationSuccessHandler() {@Overridepublic void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {httpServletResponse.sendRedirect("/showMain");}})//.failureForwardUrl("/showFail")//登录失败后的处理器.usernameParameter("myusername")//客户端的密码参数名称.passwordParameter("mypassword").failureHandler(new AuthenticationFailureHandler() {@Overridepublic void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {httpServletResponse.sendRedirect("/showFail");}});http.exceptionHandling()//.accessDeniedHandler(accessDeniedHandler);//只适用于非前端框架,适用于同步请求的方式//如果是异步请求需要使用上一种方式。.accessDeniedPage("/showAccessDenied");} }
5、异常配置
配置异常页面,如果程序异常测跳转到该页面。
如果是异步请求,如ajax,则可以实现
AccessDeniedHandler
接口@Component public class MyAccessDeniedHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {httpServletResponse.setContentType("application/json;charset=UTF-8");httpServletResponse.setStatus(httpServletResponse.SC_FORBIDDEN);PrintWriter writer = httpServletResponse.getWriter();writer.println("权限不足");writer.flush();writer.close();} }
6、配置 Remember Me
需要配置生成一个token,并将token下发给浏览器。
因此需要一个PersistentTokenRepository
类。
通过java配置类的方式来定义bean,这个bean就是persistentTokenRepository@Configuration public class RememberMeConfig {@Autowiredprivate DataSource dataSource;@Beanprotected PersistentTokenRepository getPersistentTokenRepository(){JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();//jdbcTokenRepository.setCreateTableOnStartup(true);jdbcTokenRepository.setDataSource(dataSource);return jdbcTokenRepository;} } // 在MyWebSecurityConfigurerAdapter中配置.rememberMe()private MyAccessDeniedHandler accessDeniedHandler;private PersistentTokenRepository persistentTokenRepository;protected void configure(HttpSecurity http) throws Exception {//其他配置http.rememberMe().userDetailsService(userDetailsService).tokenRepository(persistentTokenRepository).tokenValiditySeconds(10*2);//其他配置}
7、配置授权
授权一般需要依托用户的角色,对权限集合进行绑定。
因为必须先登录,因此对于登录页面直接方行,antMatchers(“/showLogin”,“/showFail”).access(“permitAll”)。
因为静态资源也要直接放行,所以对于.antMatchers(“/images/**”).permitAll() .regexMatchers(“/js/.*”).permitAll() .antMatchers(“/demo”).permitAll()这几个直接放行。
.antMatchers(“/abc”).denyAll() 所有到/abc的请求都拒绝。
最后一行的.anyRequest().authenticated(); 声明所有的请求都需要登录。
public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter{protected void configure(HttpSecurity http) throws Exception {//前面的认证配置http.authorizeRequests()//.antMatchers("/showLogin","/showFail").permitAll().antMatchers("/showLogin","/showFail").access("permitAll")//对于静态和动态请求需要分开//.antMatchers("/js/**").permitAll().antMatchers("/abc").denyAll().antMatchers("/jczl").hasAnyAuthority("demo:update")//.antMatchers("/jczl").hasAnyRole("ADMIN").antMatchers("/admin").access("@myServiceImpl.hasPermission(request,authentication)").antMatchers("/ip").hasIpAddress("192.168.7.86").antMatchers("/images/**").permitAll().regexMatchers("/js/.*").permitAll().antMatchers("/demo").permitAll().anyRequest().authenticated();
8、配置安全 csrf
为防止跨越的表单提交,在login.html 的隐藏域中需要加上
<input type=“hidden” th:value=“${_csrf.token}” name=“_csrf” th:if=“${_csrf}”>
然后在MyWebSecurityConfigurerAdapter 中继续取消 http.csrf().disable();的注释,即默认开启csrf的校验了。
WebSecurityConfigurerAdapter类:可以通过重载该类的三个configure()方法来制定Web安全的细节。
1、configure(WebSecurity):通过重载该方法,可配置Spring Security的Filter链。
2、configure(HttpSecurity):通过重载该方法,可配置如何通过拦截器保护请求。
3、configure(AuthenticationManagerBuilder):通过重载该方法,可配置user-detail(用户详细信息)服务。
1、使用基于内存的用户存储:
2、基于数据库表进行认证:
\*\* 3、基于LDAP进行认证 \*\*
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter {/*** 自定义用户认证逻辑*/@Autowiredprivate UserDetailsService userDetailsService;@Beanpublic BCryptPasswordEncoder bCryptPasswordEncoder() {return new BCryptPasswordEncoder();}/*** 身份认证接口 从写了这个接口,这里最重要的就是WebSecurityConfigurerAdapter接口下的configure方法,这个方法就是我们要实现的认证逻辑。其实也可以不重写configure方法,他默认就会去容器里面找PasswordEncoder实现类来作为认证的时候 密码加密 和数据库比较,以及userDetailsService实现类。*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {// 告诉SpringSecurity 我们要使用自己定义的userDetailsService来通过username来获取主体,并且使用了BCryptPasswordEncoder加密进行密码比较auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());} }