While it would have been best to work directly with Spring Security and SpringBoot as a “whole family barrel”, reality has always bullied those of us who can’t decide what type of architecture to build.

Apache Shiro also has its quirks. To learn more, turn to [Introduction to Apache Shiro]

1. Add Shiro dependencies

Shiro’s version of this article is as follows:

<shiro.version>1.3.2</shiro.version>
Copy the code
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>${shiro.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-web</artifactId>
    <version>${shiro.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-aspectj</artifactId>
    <version>${shiro.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>${shiro.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-quartz</artifactId>
    <version>${shiro.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>${shiro.version}</version>
</dependency>
Copy the code

2. shiroRealm

Where authorization is implemented. By inheriting AuthorizingRealm to achieve the login account password verification function

@Slf4j
public class ShiroRealm extends AuthorizingRealm {

    @Autowired
    private ShiroPermissionRepository shiroPermissionRepository;

    /** * authorized **@paramPrincipalCollection Main information *@returnAuthorization information */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        if (log.isInfoEnabled()){
            log.info("Authorization begin");
        }
        String name= (String) principalCollection.getPrimaryPrincipal();
        List<String> role = shiroPermissionRepository.queryRoleByName(name);
        if (role.isEmpty()){
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            simpleAuthorizationInfo.addRoles(role);
            return simpleAuthorizationInfo;
        }
        return null;
    }

    /** * Authentication **@paramAuthenticationToken indicates the authenticationToken *@returnCertification Results *@throwsAuthenticationException AuthenticationException */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        if (log.isInfoEnabled()){
            log.info("Authentication begin");
        }

        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

        Object principal =token.getPrincipal();
        Object credentials = token.getCredentials();

        // Verify the user name
        checkBlank(principal,"User name cannot be empty");
        // Verify password
        checkBlank(credentials,"Password cannot be empty.");

        // Verify the name
        String username = (String) principal;
        UserPO userPO = shiroPermissionRepository.findAllByName(username);
        if (userPO == null) {throw new AccountException("User name error");
        }

        // Verify password
        String password = (String) credentials;
        if(! StringUtils.equals(password,userPO.getPassword())){throw new AccountException("Password error");
        }

        return new SimpleAuthenticationInfo(principal, password, getName());
    }

    private void checkBlank(Object obj,String message){
        if (obj instanceof String){
            if (StringUtils.isBlank((String) obj)){
                throw newAccountException(message); }}else if (obj == null) {throw newAccountException(message); }}}Copy the code

3. The configuration ShiroConfig

Hand over ShiroConfig, SecurityManager, and ShiroFilterFactoryBean to Spring to manage.

  • ShiroRealm: The ShiroRealm described above
  • SecurityManager: Manages security operations for all users
  • ShiroFilterFactoryBean: Configures Shiro’s filter
@Configuration
public class ShiroConfig {

    private final static String AUTHC_STR = "authc";
    private final static String ANON_STR = "anon";

    /** * verify authorization and authentication@returnShiroRealm Authentication */
    @Bean
    public ShiroRealm shiroRealm(a){
        return new ShiroRealm();
    }

    /**
     * session manager
     *
     * @paramShiroRealm Authentication *@returnSafety management */
    @Bean
    @ConditionalOnClass(ShiroRealm.class)
    public SecurityManager securityManager(ShiroRealm shiroRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(shiroRealm);
        return securityManager;
    }

    /** * Filter factory, set the corresponding Filter condition and jump condition **@paramSecurityManager Session Management *@returnShiro Filter Factory */
    @Bean
    @ConditionalOnClass(value = {SecurityManager.class})
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        shiroFilterFactoryBean.setFilters(filterMap);

        / / URI filter
        Map<String,String> map = Maps.newLinkedHashMap();

        // Filterable interface path
        

        // All API paths are checked
        map.put("/api/**",AUTHC_STR);

        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);

        returnshiroFilterFactoryBean; }}Copy the code

3.1 Shiro filter episode

Shiro and Security are also similar in that both have their own Filter chain. Double Shiro’s source code and trace it back. Post the following:

3.1.1 ShiroFilterFactoryBean – createFilterChainManager

  protected FilterChainManager createFilterChainManager(a) {

        DefaultFilterChainManager manager = new DefaultFilterChainManager();
        Map<String, Filter> defaultFilters = manager.getFilters();
        //apply global settings if necessary:
        for (Filter filter : defaultFilters.values()) {
            applyGlobalPropertiesIfNecessary(filter);
        }

        //Apply the acquired and/or configured filters:
        Map<String, Filter> filters = getFilters();
        if(! CollectionUtils.isEmpty(filters)) {for (Map.Entry<String, Filter> entry : filters.entrySet()) {
                String name = entry.getKey();
                Filter filter = entry.getValue();
                applyGlobalPropertiesIfNecessary(filter);
                if (filter instanceof Nameable) {
                    ((Nameable) filter).setName(name);
                }
                //'init' argument is false, since Spring-configured filters should be initialized
                //in Spring (i.e. 'init-method=blah') or implement InitializingBean:
                manager.addFilter(name, filter, false); }}//build up the chains:
        Map<String, String> chains = getFilterChainDefinitionMap();
        if(! CollectionUtils.isEmpty(chains)) {for(Map.Entry<String, String> entry : chains.entrySet()) { String url = entry.getKey(); String chainDefinition = entry.getValue(); manager.createChain(url, chainDefinition); }}return manager;
    }
Copy the code

Shiro’s filter chain, as you can see from the source code, is added in the order:

  1. DefaultFilters: Shiro’s default filter chain
  2. Filters: Our custom filter chain
  3. Chains: specifically specify filters

3.1.2 DefaultFilterChainManager – addDefaultFilters

Here we see what DefaultFilterChainManager added those default filter chain, can see the main is: DefaultFilter

protected void addDefaultFilters(boolean init) {
    for (DefaultFilter defaultFilter : DefaultFilter.values()) {
        addFilter(defaultFilter.name(), defaultFilter.newInstance(), init, false); }}Copy the code

3.1.3 DefaultFilter

anon(AnonymousFilter.class),
authc(FormAuthenticationFilter.class),
authcBasic(BasicHttpAuthenticationFilter.class),
logout(LogoutFilter.class),
noSessionCreation(NoSessionCreationFilter.class),
perms(PermissionsAuthorizationFilter.class),
port(PortFilter.class),
rest(HttpMethodPermissionFilter.class),
roles(RolesAuthorizationFilter.class),
ssl(SslFilter.class),
user(UserFilter.class);
Copy the code

4. Measure it

Because the global interface is set to be validated, the expected result is that it is not accessible

map.put("/api/**",AUTHC_STR);
Copy the code

4.1 IDAL

@RestController
@RequestMapping( SYSTEM_API +"shiro")
public class ShiroIdal {

    @Resource
    private IShiroService iShiroService;


    @GetMapping
    public HttpEntity obtain(@RequestParam String name){
        returniShiroService.obtainUserByName(name); }}Copy the code

4.2 the service

@Slf4j
@Service
public class ShiroServiceImpl implements IShiroService {

    @Resource
    private ShiroPermissionRepository shiroPermissionRepository;
	
    public HttpEntity obtainUserByName(String name) {
        UserPO userPO = shiroPermissionRepository.findAllByName(name);
        returnHttpResponseSupport.success(userPO); }}Copy the code

4.3 Situation of abduction

If there is no login.jsp, it will directly report an error, personally feel too inharmonious, after all, now is the front and back end of the separation.

4.4 Allowing access

Add the following to URI filtering Map:

map.put("/api/shiro",ANON_STR);
Copy the code

Note: Add before “global Api hijacking”. And don’t use “HashMap.” Why?

4.4.1 HashMap

Before we say why, let’s understand how HashMap works.

for (Entry<String, String> entry : hashMap.entrySet()) {
   MessageFormat.format("{0} = {1}",entry.getKey(),entry.getValue());
}
Copy the code

A HashMap is a “hash order that favors random lookups.” Not in input order. Traversal output is complete, not sequential. You can even rehash() to get a more random-access internal order.

How does this affect Shiro?

Map<String, String> chains = getFilterChainDefinitionMap();
if(! CollectionUtils.isEmpty(chains)) {for(Map.Entry<String, String> entry : chains.entrySet()) { String url = entry.getKey(); String chainDefinition = entry.getValue(); manager.createChain(url, chainDefinition); }}Copy the code

In ShiroFilterFactoryBean, when shiro’s Filter chain is built, the FilterChainDefinitionMap we configured will be iterated once, And add it to the DefaultFilterChainManager.

Consider the following: if “global API hijacking” comes first, then everything under the/API /* crotch is hijacked early. Does the wheel get configured anon? If “global API hijacking” is at the front due to hash sorting of the HashMap, emmmm, then play with the hammer.

4.4.2 LinkedHashMap

Therefore, use LinkedHashMap is recommended, why? Lu source

   static class Entry<K.V> extends HashMap.Node<K.V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next); }}transient LinkedHashMap.Entry<K,V> head;
    transient LinkedHashMap.Entry<K,V> tail;
Copy the code

There are two more entries in the inner class, one to record the front Entry and one to record the back Entry. Such a bidirectional linked list structure ensures the order of insertion.

The underlying LinkedHashMap is an array plus a single necklace list plus a bidirectional list.

  • An array plus a one-way list is the structure of a HashMap,
  • Bidirectional linked list, storage insert order used.

A little off the rails, these guys must know the drop……